Micron Document
🎖️GitЯра🎖️


Displaying Raw • Download

feature/wifi-provision/src/commonMain/kotlin/org/meshtastic/feature/wifiprovision/WifiProvisionViewModel.kt renovate/actions-setup-java-5.x (f17e586c) Text, 9.92 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.wifiprovision

Tff7b72import T7ee787androidx.lifecycle.ViewModel
Tff7b72import T7ee787androidx.lifecycle.viewModelScope
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.StateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.asStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.update
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787org.koin.core.annotation.KoinViewModel
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnectionFactory
Tff7b72import T7ee787org.meshtastic.core.ble.BleScanner
Tff7b72import T7ee787org.meshtastic.core.di.CoroutineDispatchers
Tff7b72import T7ee787org.meshtastic.feature.wifiprovision.domain.NymeaWifiService
Tff7b72import T7ee787org.meshtastic.feature.wifiprovision.model.ProvisionResult
Tff7b72import T7ee787org.meshtastic.feature.wifiprovision.model.WifiNetwork

T8b949e// ---------------------------------------------------------------------------
T8b949e// UI State
T8b949e// ---------------------------------------------------------------------------

Tff7b72data Tff7b72class T56d364WifiProvisionUiStateTb4b4b4(
Tff7b72val Te6edf3phaseTb4b4b4: Te6edf3Phase Tff7b72= Te6edf3PhaseTb4b4b4.Te6edf3IdleTb4b4b4,
Tff7b72val Te6edf3networksTb4b4b4: Te6edf3ListTff7b72<Te6edf3WifiNetworkTff7b72> Tff7b72= Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4,
Tff7b72val Te6edf3errorTb4b4b4: Te6edf3WifiProvisionError? Tff7b72= Tff7b72nullTb4b4b4,
T8b949e/** Name of the BLE device we connected to, shown in the DeviceFound confirmation. */
Tff7b72val Te6edf3deviceNameTb4b4b4: Tffa657String? Tff7b72= Tff7b72nullTb4b4b4,
T8b949e/** IPv4 address reported by nymea after successful provisioning (if available). */
Tff7b72val Te6edf3ipAddressTb4b4b4: Tffa657String? Tff7b72= Tff7b72nullTb4b4b4,
T8b949e/** Provisioning outcome shown as inline status (matches web flasher pattern). */
Tff7b72val Te6edf3provisionStatusTb4b4b4: Te6edf3ProvisionStatus Tff7b72= Te6edf3ProvisionStatusTb4b4b4.Te6edf3IdleTb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72enum Tff7b72class T56d364Phase Tb4b4b4{
T8b949e/** No operation running — initial state before BLE connect. */
Te6edf3IdleTb4b4b4,

T8b949e/** Scanning BLE for a nymea device. */
Te6edf3ConnectingBleTb4b4b4,

T8b949e/** BLE device found and connected; waiting for user to proceed. */
Te6edf3DeviceFoundTb4b4b4,

T8b949e/** Fetching visible WiFi networks from the device. */
Te6edf3LoadingNetworksTb4b4b4,

T8b949e/** Connected and networks loaded — the main configuration screen. */
Te6edf3ConnectedTb4b4b4,

T8b949e/** Sending WiFi credentials to the device. */
Te6edf3ProvisioningTb4b4b4,
Tb4b4b4}

Tff7b72enum Tff7b72class T56d364ProvisionStatus Tb4b4b4{
Te6edf3IdleTb4b4b4,
Te6edf3SuccessTb4b4b4,
Te6edf3FailedTb4b4b4,
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Typed error categories for the WiFi provisioning flow.
*
* Formatted into user-visible strings in the UI layer using string resources, keeping the ViewModel free of
* locale-specific text.
*/
Tff7b72sealed Tff7b72interface T56d364WifiProvisionError Tb4b4b4{
T8b949e/** Detail message from the underlying exception (language-agnostic, typically from the BLE stack). */
Tff7b72val Te6edf3detailTb4b4b4: Tffa657String

T8b949e/** BLE connection to the provisioning device failed. */
Tff7b72data Tff7b72class T56d364ConnectFailedTb4b4b4(Tff7b72override Tff7b72val Te6edf3detailTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4: Te6edf3WifiProvisionError

T8b949e/** WiFi network scan on the device failed. */
Tff7b72data Tff7b72class T56d364ScanFailedTb4b4b4(Tff7b72override Tff7b72val Te6edf3detailTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4: Te6edf3WifiProvisionError

T8b949e/** Sending WiFi credentials to the device failed. */
Tff7b72data Tff7b72class T56d364ProvisionFailedTb4b4b4(Tff7b72override Tff7b72val Te6edf3detailTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4: Te6edf3WifiProvisionError
Tb4b4b4}

T8b949e// ---------------------------------------------------------------------------
T8b949e// ViewModel
T8b949e// ---------------------------------------------------------------------------

T8b949e/**
* ViewModel for the WiFi provisioning flow.
*
* Uses [KoinViewModel] so the instance is scoped to the navigation entry's [ViewModelStoreOwner]. A fresh
* [NymeaWifiService] (and its own [BleConnectionFactory]-backed [org.meshtastic.core.ble.BleConnection]) is created
* lazily for each provisioning session and cleaned up via [onCleared].
*/
Tf0883e@KoinViewModel
Tff7b72class T56d364WifiProvisionViewModelTb4b4b4(
Tff7b72private Tff7b72val Te6edf3bleScannerTb4b4b4: Te6edf3BleScannerTb4b4b4,
Tff7b72private Tff7b72val Te6edf3bleConnectionFactoryTb4b4b4: Te6edf3BleConnectionFactoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3dispatchersTb4b4b4: Te6edf3CoroutineDispatchersTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3ViewModelTb4b4b4(Tb4b4b4) Tb4b4b4{

Tff7b72private Tff7b72val Te6edf3_uiState Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3WifiProvisionUiStateTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3uiStateTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3WifiProvisionUiStateTff7b72> Tff7b72= Te6edf3_uiStateTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)

T8b949e/** Lazily-created service; reset on [reset]. */
Tff7b72private Tff7b72var Te6edf3serviceTb4b4b4: Te6edf3NymeaWifiService? Tff7b72= Tff7b72null

T8b949e// region Public actions (called from UI)

T8b949e/**
* Scan for the nearest nymea-networkmanager device and connect to it. Pauses at the
* [WifiProvisionUiState.Phase.DeviceFound] phase so the user can confirm before proceeding — this is the Android
* analog of the web flasher's native BLE pairing dialog.
*
* @param address Optional MAC address to target a specific device.
*/
Tff7b72fun Td2a8ffconnectToDeviceTb4b4b4(Te6edf3addressTb4b4b4: Tffa657String? Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3_uiStateTb4b4b4.Te6edf3update Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3phase Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4.Te6edf3PhaseTb4b4b4.Te6edf3ConnectingBleTb4b4b4, Te6edf3error Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4}

Te6edf3viewModelScopeTb4b4b4.Te6edf3launch Tb4b4b4{
Tff7b72val Te6edf3nymeaService Tff7b72= Te6edf3NymeaWifiServiceTb4b4b4(Te6edf3bleScannerTb4b4b4, Te6edf3bleConnectionFactoryTb4b4b4, Te6edf3dispatchersTb4b4b4.Te6edf3defaultTb4b4b4)
Te6edf3service Tff7b72= Te6edf3nymeaService

Te6edf3nymeaService
Tb4b4b4.Te6edf3connectTb4b4b4(Te6edf3addressTb4b4b4)
Tb4b4b4.Te6edf3onSuccess Tb4b4b4{ Te6edf3deviceName Tff7b72-Tff7b72>
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Tffd700$Te6edf3TAGTa5d6ff: BLE connected to: Tffd700$Te6edf3deviceNameTa5d6ff" Tb4b4b4}
Te6edf3_uiStateTb4b4b4.Te6edf3update Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3phase Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4.Te6edf3PhaseTb4b4b4.Te6edf3DeviceFoundTb4b4b4, Te6edf3deviceName Tff7b72= Te6edf3deviceNameTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72>
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Tffd700$Te6edf3TAGTa5d6ff: BLE connect failedTa5d6ff" Tb4b4b4}
Te6edf3_uiStateTb4b4b4.Te6edf3update Tb4b4b4{
Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3phase Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4.Te6edf3PhaseTb4b4b4.Te6edf3IdleTb4b4b4,
Te6edf3error Tff7b72= Te6edf3WifiProvisionErrorTb4b4b4.Te6edf3ConnectFailedTb4b4b4(Te6edf3eTb4b4b4.Te6edf3message Tff7b72?: Ta5d6ff"Ta5d6ffUnknown errorTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/** Called when the user confirms they want to scan networks after device discovery. */
Tff7b72fun Td2a8ffscanNetworksTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nymeaService Tff7b72=
Te6edf3service
Tff7b72?: Te6edf3run Tb4b4b4{
Te6edf3connectToDeviceTb4b4b4(Tb4b4b4)
Tff7b72return
Tb4b4b4}
Te6edf3viewModelScopeTb4b4b4.Te6edf3launch Tb4b4b4{ Te6edf3loadNetworksTb4b4b4(Te6edf3nymeaServiceTb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Send WiFi credentials to the device.
*
* @param ssid The target network SSID.
* @param password The network password (empty string for open networks).
*/
Tff7b72fun Td2a8ffprovisionWifiTb4b4b4(Te6edf3ssidTb4b4b4: Tffa657StringTb4b4b4, Te6edf3passwordTb4b4b4: Tffa657StringTb4b4b4, Te6edf3hiddenTb4b4b4: Tffa657Boolean Tff7b72= Tff7b72falseTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3ssidTb4b4b4.Te6edf3isBlankTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return
Tff7b72val Te6edf3nymeaService Tff7b72= Te6edf3service Tff7b72?: Tff7b72return

Te6edf3_uiStateTb4b4b4.Te6edf3update Tb4b4b4{
Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3phase Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4.Te6edf3PhaseTb4b4b4.Te6edf3ProvisioningTb4b4b4,
Te6edf3error Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3ipAddress Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3provisionStatus Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4.Te6edf3ProvisionStatusTb4b4b4.Te6edf3IdleTb4b4b4,
Tb4b4b4)
Tb4b4b4}

Te6edf3viewModelScopeTb4b4b4.Te6edf3launch Tb4b4b4{
Tff7b72when Tb4b4b4(Tff7b72val Te6edf3result Tff7b72= Te6edf3nymeaServiceTb4b4b4.Te6edf3provisionTb4b4b4(Te6edf3ssidTb4b4b4, Te6edf3passwordTb4b4b4, Te6edf3hiddenTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72is Te6edf3ProvisionResultTb4b4b4.Te6edf3Success Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Tffd700$Te6edf3TAGTa5d6ff: Provisioned successfullyTa5d6ff" Tb4b4b4}
Te6edf3_uiStateTb4b4b4.Te6edf3update Tb4b4b4{
Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3phase Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4.Te6edf3PhaseTb4b4b4.Te6edf3ConnectedTb4b4b4,
Te6edf3ipAddress Tff7b72= Te6edf3resultTb4b4b4.Te6edf3ipAddressTb4b4b4,
Te6edf3provisionStatus Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4.Te6edf3ProvisionStatusTb4b4b4.Te6edf3SuccessTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tff7b72is Te6edf3ProvisionResultTb4b4b4.Te6edf3Failure Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Tffd700$Te6edf3TAGTa5d6ff: Provision failed: Tffd700${Te6edf3resultTb4b4b4.Te6edf3messageTffd700}Ta5d6ff" Tb4b4b4}
Te6edf3_uiStateTb4b4b4.Te6edf3update Tb4b4b4{
Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3phase Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4.Te6edf3PhaseTb4b4b4.Te6edf3ConnectedTb4b4b4,
Te6edf3provisionStatus Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4.Te6edf3ProvisionStatusTb4b4b4.Te6edf3FailedTb4b4b4,
Te6edf3error Tff7b72= Te6edf3WifiProvisionErrorTb4b4b4.Te6edf3ProvisionFailedTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3messageTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/** Disconnect and close any active BLE connection. */
Tff7b72fun Td2a8ffdisconnectTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3viewModelScopeTb4b4b4.Te6edf3launch Tb4b4b4{
Te6edf3serviceTff7b72?.Te6edf3closeTb4b4b4(Tb4b4b4)
Te6edf3service Tff7b72= Tff7b72null
Te6edf3_uiStateTb4b4b4.Te6edf3value Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// endregion

Tff7b72override Tff7b72fun Td2a8ffonClearedTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72superTb4b4b4.Te6edf3onClearedTb4b4b4(Tb4b4b4)
Te6edf3serviceTff7b72?.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}

T8b949e// region Private helpers

Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffloadNetworksTb4b4b4(Te6edf3nymeaServiceTb4b4b4: Te6edf3NymeaWifiServiceTb4b4b4) Tb4b4b4{
Te6edf3_uiStateTb4b4b4.Te6edf3update Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3phase Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4.Te6edf3PhaseTb4b4b4.Te6edf3LoadingNetworksTb4b4b4) Tb4b4b4}

Te6edf3nymeaService
Tb4b4b4.Te6edf3scanNetworksTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3onSuccess Tb4b4b4{ Te6edf3networks Tff7b72-Tff7b72>
Te6edf3_uiStateTb4b4b4.Te6edf3update Tb4b4b4{
Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3phase Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4.Te6edf3PhaseTb4b4b4.Te6edf3ConnectedTb4b4b4, Te6edf3networks Tff7b72= Te6edf3deduplicateBySsidTb4b4b4(Te6edf3networksTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3onFailure Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72>
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Tffd700$Te6edf3TAGTa5d6ff: scanNetworks failedTa5d6ff" Tb4b4b4}
Te6edf3_uiStateTb4b4b4.Te6edf3update Tb4b4b4{
Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3phase Tff7b72= Te6edf3WifiProvisionUiStateTb4b4b4.Te6edf3PhaseTb4b4b4.Te6edf3ConnectedTb4b4b4,
Te6edf3error Tff7b72= Te6edf3WifiProvisionErrorTb4b4b4.Te6edf3ScanFailedTb4b4b4(Te6edf3eTb4b4b4.Te6edf3message Tff7b72?: Ta5d6ff"Ta5d6ffUnknown errorTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// endregion

Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72const Tff7b72val Te6edf3TAG Tff7b72= Ta5d6ff"Ta5d6ffWifiProvisionViewModelTa5d6ff"

T8b949e/**
* Deduplicate networks by SSID, keeping the entry with the strongest signal for each. Since we only send SSID
* (not BSSID) to the device, showing duplicates is confusing.
*/
Tff7b72internal Tff7b72fun Td2a8ffdeduplicateBySsidTb4b4b4(Te6edf3networksTb4b4b4: Te6edf3ListTff7b72<Te6edf3WifiNetworkTff7b72>Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3WifiNetworkTff7b72> Tff7b72= Te6edf3networks
Tb4b4b4.Te6edf3groupBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3ssid Tb4b4b4}
Tb4b4b4.Te6edf3map Tb4b4b4{ Tb4b4b4(Te6edf3_Tb4b4b4, Te6edf3entriesTb4b4b4) Tff7b72-Tff7b72> Te6edf3entriesTb4b4b4.Te6edf3maxBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3signalStrength Tb4b4b4} Tb4b4b4}
Tb4b4b4.Te6edf3sortedByDescending Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3signalStrength Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.08s